package island

import (
	"bytes"
	"fmt"
	"reflect"
)

func (container *base) GetStdio() (chan []byte, chan []byte, chan []byte) {
	stdin := make(chan []byte, 16)
	stdout := make(chan []byte, 16)
	stderr := make(chan []byte, 16)

	container.attachedIOLock.Lock()
	if container.attachedIO == nil {
		container.attachedIO = make(map[chan []byte]*[2]chan []byte)
	}

	container.attachedIO[stdin] = &[2]chan []byte{stdout, stderr}

	if !container.stdin {
		container.stdin = true
		go stdinHandle(container)
	}

	container.attachedIOLock.Unlock()

	return stdin, stdout, stderr
}

func stdoutHandle(container *base) {
	for {
		buf := make([]byte, 4096)
		n, err := container.process.StdoutReader.Read(buf)
		if err != nil {
			container.poststopHook()
			// 需要做一些清理
			container.attachedIOLock.Lock()
			for _, io := range container.attachedIO {
				close(io[0])
				close(io[1])
			}
			container.attachedIO = nil
			container.attachedIOLock.Unlock()
			return
		}
		chunk := buf[:n]
		fmt.Print(string(chunk))
		_, _ = container.logs.Stdout.Write(chunk)
		container.attachedIOLock.RLock()
		for _, stdio := range container.attachedIO {
			stdio[0] <- chunk
		}
		container.attachedIOLock.RUnlock()
	}
}

func stdinHandle(container *base) {

	defer func() {
		container.stdin = false
	}()

	container.attachedIOLock.RLock()
	stdinList := make([]chan []byte, 0, len(container.attachedIO))
	listenList := make([]reflect.SelectCase, 0, len(container.attachedIO))
	for io, _ := range container.attachedIO {
		stdinList = append(stdinList, io)
		listenList = append(listenList, reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(io)})
	}
	container.attachedIOLock.RUnlock()
	for {
		chosen, value, ok := reflect.Select(listenList)
		// ok will be true if the channel has not been closed.
		ch := stdinList[chosen]
		if ok {
			msg := bytes.ReplaceAll(value.Bytes(), []byte{'\r'}, []byte{'\n'})
			_, _ = container.logs.Stdin.Write(msg)
			fmt.Print(string(msg))
			// write to pipe
			_, err := container.process.StdinWriter.Write(msg)
			if err != nil {
				logger.WithField("id", container.ID).Error("failed to write container's stdin")
				// 容器退出，需要执行一些清理动作
				container.attachedIOLock.Lock()
				for _, io := range container.attachedIO {
					close(io[0])
					close(io[1])
				}
				container.attachedIO = nil
				container.attachedIOLock.Unlock()
				return
			}
		} else {
			container.attachedIOLock.Lock()
			if _, ok := container.attachedIO[ch]; ok && container.attachedIO[ch][0] != nil {
				// 容器关闭由stdout触发清除，可能list已经被释放了，因此需要额外判断一下
				close(container.attachedIO[ch][0])
				close(container.attachedIO[ch][1])
			}
			delete(container.attachedIO, ch)

			// 没有任何attached stdin时则退出
			if len(container.attachedIO) == 0 {
				container.attachedIOLock.Unlock()
				return
			}

			// 更新监听列表
			stdinList = make([]chan []byte, 0, len(container.attachedIO))
			listenList = make([]reflect.SelectCase, 0, len(container.attachedIO))
			for io, _ := range container.attachedIO {
				stdinList = append(stdinList, io)
				listenList = append(listenList, reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(io)})
			}
			container.attachedIOLock.Unlock()
		}
	}
}
